package io.github.iogogogo.commons.util;

import com.google.common.collect.Lists;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import java.io.Serializable;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.*;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by tao.zeng on 2024/11/7.
 */
@Slf4j
public class DateTimeUtils {


    /**
     * 获取当前的日期和时间
     * <p>
     * 此方法用于获取当前系统时间，包括日期和时间戳，精确到纳秒级
     * 它基于系统时钟，因此受系统时钟影响，可能存在时间漂移
     *
     * @return 当前的日期和时间，类型为LocalDateTime
     */
    public static LocalDateTime nowDateTime() {
        return LocalDateTime.now();
    }


    /**
     * 获取当前日期
     * <p>
     * 此方法用于获取当前系统的日期，使用Java 8的LocalDate类来表示日期
     * 它提供了一个静态方法now()，用于获取当前日期
     *
     * @return LocalDate 返回当前日期，不包含时间信息
     */
    public static LocalDate nowDate() {
        return LocalDate.now();
    }

    /**
     * 获取当前系统时间
     * <p>
     * 该方法用于获取当前系统时间，并以LocalTime对象的形式返回
     * 主要用途是在于可以方便地获取当前时间，而无需直接与系统时钟交互
     *
     * @return LocalTime 返回当前系统时间
     */
    public static LocalTime nowTime() {
        return LocalTime.now();
    }


    /**
     * 获取当前时间的时间戳
     * <p>
     * 此方法用于获取当前日期和时间，并将其转换为Unix时间戳（从1970年1月1日00:00:00 UTC开始的毫秒数）
     * 使用场景包括记录事件发生的时间、同步数据等需要时间戳的情况
     *
     * @return 当前时间的Unix时间戳，单位为毫秒
     */
    public static long now() {
        return toEpochMilli(nowDateTime());
    }

    /**
     * 格式化LocalDate对象为指定模式的字符串
     * 此方法用于将LocalDate对象转换为符合指定格式的日期字符串
     *
     * @param date    LocalDate对象，代表需要格式化的日期
     * @param pattern 日期格式模式，用于定义输出的日期字符串格式
     * @return 格式化后的日期字符串
     */
    public static String formatLocalDate(LocalDate date, String pattern) {
        // 创建DateTimeFormatter对象，使用给定的模式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        // 使用formatter将LocalDate对象格式化为字符串并返回
        return date.format(formatter);
    }

    /**
     * 格式化LocalDateTime对象为字符串
     * 根据提供的模式将LocalDateTime对象格式化为日期时间字符串
     *
     * @param dateTime LocalDateTime对象，需要格式化的日期时间
     * @param pattern  字符串模式，定义了日期时间的格式规则
     * @return 格式化后的日期时间字符串
     */
    public static String formatLocalDateTime(LocalDateTime dateTime, String pattern) {
        // 创建DateTimeFormatter对象，使用给定的模式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        // 使用formatter将LocalDateTime对象格式化为字符串并返回
        return dateTime.format(formatter);
    }


    /**
     * 尝试将字符串解析为LocalDateTime对象。
     * <p>
     * 此方法根据提供的字符串值、语言环境和格式模式列表，尝试将其解析为日期时间对象。
     * 它首先检查输入字符串是否为空，然后根据输入字符串的格式选择合适的解析器进行解析。
     * 如果输入字符串符合Unix时间戳格式，直接使用相应的解析器进行解析。
     * 否则，它将使用提供的格式模式列表创建一个DateTimeFormatter，并使用它来解析输入字符串。
     *
     * @param value   待解析的字符串。
     * @param locale  语言环境，用于解析字符串。
     * @param pattern 格式模式列表，用于创建DateTimeFormatter。
     * @return 解析后的LocalDateTime对象。
     * @throws NullPointerException 如果输入字符串为空。
     */
    public static LocalDateTime tryParse(String value, Locale locale, List<String> pattern) {

        // 检查输入字符串是否为空，如果为空则抛出异常
        if (StringUtils.isEmpty(value)) throw new NullPointerException("value is not null.");

        // 根据输入字符串创建一个时间戳解析器
        TimestampParser timestampParser = TimestampParserFactory.makeParser(value);

        // 如果输入字符串符合Unix时间戳格式，直接使用相应的解析器进行解析
        if (timestampParser instanceof UnixEpochParser || timestampParser instanceof UnixMillisEpochParser) {
            return timestampParser.parse(null);
        } else {
            // 过滤掉空的格式模式，确保只使用有效的模式
            List<String> patterns = pattern.stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());
            // 检查是否有有效的格式模式
            boolean sign = CollectionUtils.isNotEmpty(patterns);
            // 如果没有有效的格式模式，则使用默认的模式
            List<String> collect = sign ? patterns : TimestampParser.defaultPattern();

            // 根据格式模式列表创建DateTimeFormatter对象
            List<DateTimeFormatter> timeFormatters = collect.stream()
                    .peek(x -> {
                        // 如果日志级别为debug，则记录每个格式模式的初始化过程
                        if (log.isDebugEnabled())
                            log.debug("init parser ofPattern: {}", x);
                    }).map(x -> DateTimeFormatter.ofPattern(x, locale)).collect(Collectors.toList());

            // 使用创建的DateTimeFormatter对象解析输入字符串
            return timestampParser.parse(timeFormatters);
        }
    }


    /**
     * 尝试将字符串解析为LocalDateTime对象
     * 此方法重载允许接受可变长度的参数模式，以提高灵活性和可读性
     *
     * @param value   需要解析的日期时间字符串
     * @param locale  用于解析日期时间字符串的本地化设置
     * @param pattern 一个或多个日期时间格式模式，用于尝试解析字符串
     * @return 如果解析成功，则返回LocalDateTime对象；否则返回null
     */
    public static LocalDateTime tryParse(String value, Locale locale, String... pattern) {
        // 将可变长度的参数转换为列表，以便传递给另一个重载的方法
        return tryParse(value, locale, Stream.of(pattern).collect(Collectors.toList()));
    }

    /**
     * 尝试将字符串解析为LocalDateTime对象
     * 此方法重载了另一个具有相同名称的方法，但专注于使用中文本地化
     * 它通过调用具有更广泛用途的方法来实现功能
     *
     * @param value   需要解析的字符串
     * @param pattern 日期时间格式的列表，用于尝试解析字符串
     * @return 如果解析成功，则返回LocalDateTime对象；否则返回null
     */
    public static LocalDateTime tryParse(String value, List<String> pattern) {
        return tryParse(value, Locale.CHINA, pattern);
    }

    /**
     * 尝试将给定的字符串按照指定的日期时间格式数组进行解析，返回解析成功的LocalDateTime对象
     * 如果所有提供的格式都无法解析给定的字符串，则返回null
     * 此方法简化了只提供单个格式字符串的调用过程
     *
     * @param value   需要解析的日期时间字符串
     * @param pattern 日期时间格式数组，用于尝试解析日期时间字符串
     * @return 解析成功的LocalDateTime对象，如果解析失败则返回null
     */
    public static LocalDateTime tryParse(String value, String... pattern) {
        // 将可变参数转换为List，以便调用重载方法
        return tryParse(value, Stream.of(pattern).collect(Collectors.toList()));
    }

    /**
     * 给定一个LocalDate对象和一个天数，返回加上天数后的日期
     * 此方法利用LocalDate的plusDays方法，简化了日期的计算过程
     *
     * @param date 起始日期
     * @param days 需要增加的天数，可以是负数表示减少天数
     * @return 增加天数后的日期
     */
    public static LocalDate plusDays(LocalDate date, long days) {
        return date.plusDays(days);
    }

    /**
     * 从给定日期中减去指定的天数
     * 此方法用于计算日期减去一定天数后的结果，便于在日期计算中使用
     *
     * @param date 起始日期，不能为空
     * @param days 要减去的天数，可以是任意非负整数
     * @return 计算得到的日期，该日期为起始日期减去指定天数的结果
     */
    public static LocalDate minusDays(LocalDate date, long days) {
        return date.minusDays(days);
    }

    /**
     * 获取指定年份和月份的天数
     * 此方法使用YearMonth类来计算特定年份和月份的天数，考虑到闰年和不同月份的天数差异
     *
     * @param year  年份，例如2023
     * @param month 月份，1到12之间的数字代表一年中的月份
     * @return 指定年份和月份的天数
     */
    public static int getDaysInMonth(int year, int month) {
        return YearMonth.of(year, month).lengthOfMonth();
    }

    /**
     * 获取给定日期的年份
     *
     * @param date LocalDate对象，表示要获取年份的日期
     * @return int 返回给定日期的年份
     */
    public static int getYear(LocalDate date) {
        return date.getYear();
    }

    /**
     * 获取给定日期对应的月份
     * 此方法用于从Java 8引入的LocalDate对象中提取月份
     * 它返回的是一个整数值，表示一年中的月份，例如1月返回1，12月返回12
     *
     * @param date LocalDate对象，表示一个日期
     * @return 一个整数，表示传入日期的月份
     */
    public static int getMonth(LocalDate date) {
        return date.getMonthValue();
    }

    /**
     * 获取指定日期对应的月份中的天数
     *
     * @param date LocalDate对象，表示特定的日期
     * @return int 返回该日期在月份中的天数
     */
    public static int getDayOfMonth(LocalDate date) {
        return date.getDayOfMonth();
    }

    /**
     * 获取LocalDateTime对象的小时值
     *
     * @param dateTime LocalDateTime对象，用于获取小时值
     * @return int 返回给定dateTime对象的小时值
     */
    public static int getHour(LocalDateTime dateTime) {
        return dateTime.getHour();
    }

    /**
     * 获取LocalDateTime对象的分钟数
     *
     * @param dateTime LocalDateTime对象，用于获取分钟数
     * @return int 返回传入时间的分钟数
     */
    public static int getMinute(LocalDateTime dateTime) {
        return dateTime.getMinute();
    }

    /**
     * 获取LocalDateTime对象的秒数
     *
     * @param dateTime LocalDateTime对象，用于获取秒数
     * @return 返回传入时间的秒数
     */
    public static int getSecond(LocalDateTime dateTime) {
        return dateTime.getSecond();
    }

    /**
     * 计算两个时间点之间的天数差
     *
     * @param start 起始时间点
     * @param end   结束时间点
     * @return 两个时间点之间的天数差
     */
    public static long betweenDays(LocalDateTime start, LocalDateTime end) {
        // 使用 ChronoUnit.DAYS.between方法计算两个时间点之间的天数差
        return ChronoUnit.DAYS.between(start, end);
    }

    /**
     * 判断给定的日期是否在今天之前
     *
     * @param date 要判断的日期
     * @return 如果给定日期在今天之前，则返回true；否则返回false
     */
    public static boolean isBefore(LocalDate date) {
        // 使用LocalDate的isBefore方法比较给定日期与当前日期
        return date.isBefore(LocalDate.now());
    }

    /**
     * 判断给定的日期是否在当前日期之后
     *
     * @param date 要判断的日期
     * @return 如果给定日期在当前日期之后，则返回true；否则返回false
     */
    public static boolean isAfter(LocalDate date) {
        // 检查输入日期是否在当前系统日期之后
        return date.isAfter(LocalDate.now());
    }

    /**
     * 判断两个日期时间是否相同
     * 此方法使用LocalDateTime的isEqual方法来比较两个日期时间对象
     * 它仅考虑日期时间是否完全相同，不考虑时区或其他因素
     *
     * @param date1 第一个日期时间对象
     * @param date2 第二个日期时间对象
     * @return 如果两个日期时间完全相同，则返回true；否则返回false
     */
    public static boolean isEqual(LocalDateTime date1, LocalDateTime date2) {
        return date1.isEqual(date2);
    }

    /**
     * 判断给定日期是否在今天之前或与今天相等
     * 此方法用于比较给定的LocalDate对象是否在当前日期之前，或者是否与当前日期相等
     * 它结合了对日期的“之前”和“等于”两种情况的判断，提供了一种简便的方式来进行日期比较
     *
     * @param date 要比较的日期，类型为LocalDate
     * @return 如果给定日期在今天之前或与今天相等，则返回true；否则返回false
     */
    public static boolean isBeforeOrEqual(LocalDate date) {
        return date.isBefore(LocalDate.now()) || date.isEqual(LocalDate.now());
    }


    /**
     * 检查给定日期是否在今天之后或与今天相等
     * 此方法用于比较给定的LocalDate对象是否在当前日期之后，或者与当前日期相等
     * 它结合了对日期的“之后”和“等于”两种情况的判断，提供了一种简洁的方式来评估日期是否“不早于”今天
     *
     * @param date 要比较的日期，类型为LocalDate
     * @return 如果给定日期在今天之后或与今天相等，则返回true；否则返回false
     */
    public static boolean isAfterOrEqual(LocalDate date) {
        // 检查给定日期是否在今天之后
        boolean isAfterToday = date.isAfter(LocalDate.now());
        // 检查给定日期是否与今天相等
        boolean isEqualToToday = date.isEqual(LocalDate.now());
        // 如果给定日期在今天之后或与今天相等，则返回true
        return isAfterToday || isEqualToToday;
    }

    /**
     * 检查给定日期是否在指定的日期范围内
     *
     * @param date      要检查的日期
     * @param startDate 日期范围的起始日期
     * @param endDate   日期范围的结束日期
     * @return 如果给定日期在指定范围内，则返回true；否则返回false
     */
    public static boolean dateInRange(LocalDate date, LocalDate startDate, LocalDate endDate) {
        // 检查日期是否在范围内，即日期大于起始日期且小于结束日期
        return date.isAfter(startDate) && date.isBefore(endDate);
    }

    /**
     * 根据给定的日期计算并返回对应的星期几
     * 此方法利用了Java 8日期和时间API中的LocalDate类和DayOfWeek枚举
     * 来简化对日期操作的复杂性，并提高代码的可读性和易维护性
     *
     * @param date 一个LocalDate对象，表示需要计算星期几的日期
     * @return DayOfWeek枚举，表示给定日期是星期几
     */
    public static DayOfWeek getDayOfWeek(LocalDate date) {
        return date.getDayOfWeek();
    }

    /**
     * 判断指定的年份是否为闰年
     *
     * @param year 待判断的年份
     * @return 如果指定年份是闰年，则返回true；否则返回false
     */
    public static boolean isLeapYear(int year) {
        // 使用Year类的of静态方法创建一个Year对象，并调用isLeap方法判断是否为闰年
        return Year.of(year).isLeap();
    }

    /**
     * 获取下一个工作日的日期
     * 该方法会跳过周六和周日，如果输入的日期是周末，则返回周一的日期
     * 如果输入的日期是工作日，则直接返回该日期的后一天，除非后一天是周末
     *
     * @param date 输入的日期，用于计算下一个工作日
     * @return 下一个工作日的日期，如果输入的是周末，则返回周一的日期
     */
    public static LocalDate getNextWorkingDay(LocalDate date) {
        // 循环增加日期，直到找到下一个工作日
        do {
            // 增加一天
            date = date.plusDays(1);
        } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY);
        // 返回下一个工作日的日期
        return date;
    }

    /**
     * 获取给定日期的前一个工作日
     * 如果给定日期为周六或周日，则分别返回上周五或上周四
     * 此方法用于在项目管理、金融交易等场景中确定工作日
     *
     * @param date 输入的日期，用于计算前一个工作日
     * @return 前一个工作日的日期
     */
    public static LocalDate getPreviousWorkingDay(LocalDate date) {
        // 循环减去日期直到找到工作日（非周六和周日）
        do {
            date = date.minusDays(1);
        } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY);
        return date;
    }

    /**
     * 根据出生日期计算年龄
     *
     * @param birthDate 出生日期，用于计算从该日期到当前日期的年数
     * @return 返回从出生日期到当前日期的年数，即年龄
     */
    public static int getAge(LocalDate birthDate) {
        // 计算从出生日期到当前日期的时间段，并返回这段时期的年数
        return Period.between(birthDate, nowDate()).getYears();
    }

    /**
     * 根据给定的日期计算并返回该日期所属的季度
     * 一年分为四个季度，每个季度包含三个月这个方法通过计算给定日期的月份来确定它属于哪个季度
     *
     * @param date LocalDate对象，表示需要计算季度的日期
     * @return 返回一个整数，表示给定日期所属的季度（1, 2, 3, 4）
     */
    public static int getQuarter(LocalDate date) {
        // 计算季度：月份值减1，然后整除3，再加1
        // 例如，1月至3月为第1季度，4月至6月为第2季度，以此类推
        return (date.getMonthValue() - 1) / 3 + 1;
    }

    /**
     * 获取指定日期所在季度的第一天
     *
     * @param date LocalDate类型的日期，用于确定季度
     * @return 返回该季度的第一天，类型为LocalDate
     */
    public static LocalDate getFirstDayOfQuarter(LocalDate date) {
        // 计算指定日期所在季度的第一个月
        int month = (date.getMonthValue() - 1) / 3 * 3 + 1;
        // 返回指定季度的第一天
        return LocalDate.of(date.getYear(), month, 1);
    }

    /**
     * 获取给定日期所在季度的最后一天
     *
     * @param date LocalDate对象，代表输入的日期
     * @return LocalDate对象，代表输入日期所在季度的最后一天
     */
    public static LocalDate getLastDayOfQuarter(LocalDate date) {
        // 计算给定日期所在季度的最后一个月
        int month = (date.getMonthValue() - 1) / 3 * 3 + 3;
        // 返回该季度最后一个月的最后一天
        return LocalDate.of(date.getYear(), month, Month.of(month).maxLength());
    }

    /**
     * 判断给定日期是否为工作日
     * <p>
     * 此方法用于确定指定的日期是否为星期一至星期五之间的工作日
     * 它通过排除周六和周日来判断日期是否为工作日
     *
     * @param date LocalDate类型的日期对象，表示需要判断的日期
     * @return boolean类型返回值，如果指定日期是工作日则返回true，否则返回false
     */
    public static boolean isWeekday(LocalDate date) {
        return date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY;
    }

    /**
     * 判断给定日期是否为周末
     *
     * @param date LocalDate类型的日期，用于判断是否为周末
     * @return boolean类型，如果给定日期是周六或周日，则返回true，否则返回false
     */
    public static boolean isWeekend(LocalDate date) {
        // 判断日期是周六还是周日，如果是，则返回true
        return date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY;
    }

    /**
     * 获取给定日期所在周的第一天（周一）
     * 此方法用于标准化日期，以便于日历或时间表的展示
     * 选择周一作为每周的开始，这是许多国家和地区的通用做法
     *
     * @param date LocalDate对象，代表输入的日期
     * @return LocalDate对象，代表输入日期所在周的周一
     */
    public static LocalDate getFirstDayOfWeek(LocalDate date) {
        // 将给定日期调整到其所在周的周一
        return date.with(DayOfWeek.MONDAY);
    }

    /**
     * 获取给定日期所在周的最后一天（星期日）
     *
     * @param date LocalDate类型的日期，表示需要计算的周的某一天
     * @return LocalDate类型的日期，表示该周的最后一天（星期日）
     */
    public static LocalDate getLastDayOfWeek(LocalDate date) {
        // 使用with方法将给定日期调整到同一周的星期日
        return date.with(DayOfWeek.SUNDAY);
    }

    /**
     * 获取指定日期所在月份的第一天
     *
     * @param date 一个LocalDate对象，用于确定月份
     * @return 返回指定月份的第一天的日期对象
     */
    public static LocalDate getFirstDayOfMonth(LocalDate date) {
        // 使用withDayOfMonth方法将日期设置为该月的第一天
        return date.withDayOfMonth(1);
    }

    /**
     * 获取给定日期所在月份的最后一天
     *
     * @param date LocalDate类型的日期对象，用于确定月份
     * @return 返回该月份的最后一天日期
     */
    public static LocalDate getLastDayOfMonth(LocalDate date) {
        // 使用withDayOfMonth方法结合lengthOfMonth方法，将给定日期调整到该月最后一天
        return date.withDayOfMonth(date.lengthOfMonth());
    }

    /**
     * 获取指定日期所在年的第一天
     *
     * @param date LocalDate对象，用于确定年份
     * @return 返回该年年的第一天，类型为LocalDate
     */
    public static LocalDate getFirstDayOfYear(LocalDate date) {
        // 使用withDayOfYear方法将日期设置为该年的第一天
        return date.withDayOfYear(1);
    }

    /**
     * 获取指定日期所在年的最后一天
     *
     * @param date 输入的日期对象，用于确定年份
     * @return 返回该年最后一天的日期对象
     */
    public static LocalDate getLastDayOfYear(LocalDate date) {
        // 使用输入日期计算并返回该年的最后一天
        return date.withDayOfYear(date.lengthOfYear());
    }

    /**
     * 计算给定日期所在月份的工作日天数
     * 工作日是指周一到周五，排除周末（周六和周日）
     *
     * @param date LocalDate对象，代表需要计算的日期
     * @return int 返回给定月份的工作日天数
     */
    public static int getWeekdayCountOfMonth(LocalDate date) {
        // 初始化工作日计数器为0
        int weekdayCount = 0;
        // 获取给定日期所在月份的第一天
        LocalDate firstDayOfMonth = getFirstDayOfMonth(date);
        // 获取给定日期所在月份的最后一天
        LocalDate lastDayOfMonth = getLastDayOfMonth(date);

        // 遍历从月初到月末的每一天
        while (!firstDayOfMonth.isAfter(lastDayOfMonth)) {
            // 判断当前天是否为工作日
            if (isWeekday(firstDayOfMonth)) {
                // 如果是工作日，则计数器加1
                weekdayCount++;
            }
            // 移到下一天
            firstDayOfMonth = firstDayOfMonth.plusDays(1);
        }

        // 返回该月的工作日总天数
        return weekdayCount;
    }

    /**
     * 计算某月的周末天数
     * 通过遍历从月初到月末的每一天，检查是否为周末，来统计周末的总天数
     *
     * @param date 一个LocalDate对象，用于确定要计算周末天数的月份
     * @return 该月的周末天数
     */
    public static int getWeekendCountOfMonth(LocalDate date) {
        // 初始化周末计数器
        int weekendCount = 0;
        // 获取该月的第一天
        LocalDate firstDayOfMonth = getFirstDayOfMonth(date);
        // 获取该月的最后一天
        LocalDate lastDayOfMonth = getLastDayOfMonth(date);

        // 遍历该月的每一天，直到超过该月的最后一天
        while (!firstDayOfMonth.isAfter(lastDayOfMonth)) {
            // 如果当前天是周末，则周末计数器加一
            if (isWeekend(firstDayOfMonth)) {
                weekendCount++;
            }
            // 将当前天增加一天，以便下一次遍历
            firstDayOfMonth = firstDayOfMonth.plusDays(1);
        }

        // 返回该月的周末总天数
        return weekendCount;
    }

    /**
     * 计算给定年份的所有工作日数量
     * 工作日定义为不是周末的日期
     * 此方法从指定日期所在年份的年初开始遍历到年末，逐日检查是否为工作日，并计数
     *
     * @param date 指定年份的一个日期对象该日期用于确定是哪一年，其具体月份和日期被忽略
     * @return 指定年份的工作日总数
     */
    public static int getWeekdayCountOfYear(LocalDate date) {
        // 初始化工作日计数器
        int weekdayCount = 0;
        // 获取指定年份的第一天
        LocalDate firstDayOfYear = getFirstDayOfYear(date);
        // 获取指定年份的最后一天
        LocalDate lastDayOfYear = getLastDayOfYear(date);

        // 遍历年份中的每一天，直到年末
        while (!firstDayOfYear.isAfter(lastDayOfYear)) {
            // 如果当前日期是工作日，则计数器加一
            if (isWeekday(firstDayOfYear)) {
                weekdayCount++;
            }
            // 移动到下一天
            firstDayOfYear = firstDayOfYear.plusDays(1);
        }

        // 返回工作日总数
        return weekdayCount;
    }


    /**
     * 将LocalDateTime转换为Instant
     * 此方法用于将本地日期时间转换为瞬时时间（UTC时间）
     * 主要通过获取系统默认时区并将其应用到LocalDateTime，从而转换为Instant
     *
     * @param localDateTime 本地日期时间
     * @return 转换后的瞬时时间（UTC时间）
     */
    public static Instant toInstant(LocalDateTime localDateTime) {
        // 获取系统默认时区
        ZoneId zone = ZoneId.systemDefault();
        // 将本地日期时间与系统默认时区结合，转换为瞬时时间（UTC时间）
        return localDateTime.atZone(zone).toInstant();
    }


    /**
     * 将LocalDateTime转换为自1970年1月1日0时0分0秒以来的毫秒数
     * 这个方法用于将LocalDateTime对象转换为Epoch毫秒，以便于时间的统一和标准化表示
     * 它利用了toInstant方法将LocalDateTime转换为Instant对象，然后使用toEpochMilli方法获取Epoch毫秒
     *
     * @param localDateTime 要转换的LocalDateTime对象，代表一个日期和时间
     * @return 自1970年1月1日0时0分0秒以来的毫秒数，表示转换后的时间
     */
    public static long toEpochMilli(LocalDateTime localDateTime) {
        return toInstant(localDateTime).toEpochMilli();
    }


    /**
     * 将LocalDateTime转换为自1970年1月1日0时0分0秒（UTC）以来的秒数
     * 这个方法主要用于时间转换，将一个本地日期时间对象转换为Unix时间戳（单位为秒）
     *
     * @param localDateTime 要转换的本地日期时间对象
     * @return 自1970年1月1日0时0分0秒（UTC）以来的秒数
     */
    public static long toEpochSecond(LocalDateTime localDateTime) {
        // 将LocalDateTime转换为Instant，然后获取自Unix纪元以来的毫秒数，并转换为秒
        return toEpochMilli(localDateTime) / 1000;
    }


    /**
     * 创建一个表示特定毫秒瞬间的Instant对象
     * 该方法基于Unix纪元时间（1970年1月1日0时0分0秒UTC）创建时间点
     *
     * @param millis Unix纪元时间以来的毫秒数
     * @return 表示给定毫秒瞬间的Instant对象
     */
    public static Instant ofEpochMilli(long millis) {
        return Instant.ofEpochMilli(millis);
    }


    /**
     * 创建一个表示给定epoch秒数的Instant对象
     * 此方法用于将给定的秒数转换为Instant对象，该对象表示时间线上的一个点
     * 它是静态的，因此可以直接通过类名调用，无需实例化对象
     *
     * @param second 从1970年1月1日0时0分0秒（UTC）开始的秒数
     * @return Instant对象，表示给定的epoch秒数
     */
    public static Instant ofEpochSecond(long second) {
        return Instant.ofEpochSecond(second);
    }


    /**
     * 将毫秒时间戳转换为Java 8的LocalDateTime对象
     * 此方法用于处理需要高时间精度（毫秒级）的情况
     * 选择使用LocalDateTime是因为它提供了更丰富的日期和时间操作功能
     *
     * @param timestamp 毫秒时间戳，自1970年1月1日0时0分0秒（UTC）以来的毫秒数
     * @return 对应的LocalDateTime对象，表示相同的时间点
     */
    public static LocalDateTime toJava8Millis(long timestamp) {
        return toJava8(ofEpochMilli(timestamp));
    }


    /**
     * 将毫秒时间戳转换为Java 8的LocalDateTime对象，精确到秒
     * 此方法的目的是提供一种方式，将传统的时间戳表示法转换为Java 8日期/时间API的等效表示
     * 它通过将时间戳乘以1000来将毫秒转换为秒，然后使用Instant.ofEpochSecond方法来创建Instant对象，
     * 最后将Instant对象转换为LocalDateTime对象
     *
     * @param timestamp 时间戳，自1970年1月1日0时0分0秒（UTC）以来的毫秒数
     * @return 对应于给定时间戳的LocalDateTime对象，精确到秒
     */
    public static LocalDateTime toJava8Second(long timestamp) {
        return toJava8(ofEpochSecond(timestamp * 1000));
    }


    /**
     * 将Instant对象转换为Java 8的LocalDateTime对象
     * 此方法用于处理时间转换，将Instant类型的时间转换为LocalDateTime类型，
     * 并考虑了时区的影响，使得时间转换更加准确
     *
     * @param instant Instant对象，表示一个瞬间的时间值
     * @return LocalDateTime对象，表示转换后的本地日期和时间
     */
    public static LocalDateTime toJava8(Instant instant) {
        // 获取系统默认的时区
        ZoneId zone = ZoneId.systemDefault();
        // 使用默认时区将Instant转换为LocalDateTime
        return LocalDateTime.ofInstant(instant, zone);
    }


    /**
     * 将java.util.Date对象转换为Java 8的LocalDateTime对象
     * 此方法利用Instant对象作为中间步骤进行转换
     *
     * @param date 要转换的Java.util.Date对象
     * @return 转换后的Java 8 LocalDateTime对象
     */
    public static LocalDateTime toJava8(Date date) {
        return toJava8(date.toInstant());
    }

    /**
     * Millis convert tuple 2.
     *
     * @param value the value
     * @param unit  the unit
     * @return the tuple 2
     */
    public static Tuple2<Long, String> millisConvert(long value, String unit) {
        return convert(value, unit, "MS");
    }


    /**
     * 将给定的时间值从一个单位转换到另一个单位，并以人类可读的格式返回.
     * 此方法支持的时间单位包括秒、分钟、小时、天以及毫秒.
     *
     * @param value      需要转换的时间值.
     * @param srcUnit    输入时间值的单位.
     * @param targetUnit 目标时间单位.
     * @return 返回一个包含转换后时间值和对应单位字符串的Tuple2对象.
     */
    public static Tuple2<Long, String> convert(long value, String srcUnit, String targetUnit) {
        // 初始化结果变量
        long result;
        // 获取目标时间单位的TimeUnit枚举对象
        TimeUnit timeUnit = getUnit(targetUnit);

        // 根据源时间单位进行转换
        switch (srcUnit.toUpperCase()) {
            case "SECONDS":
                // 将秒转换为目标单位
                result = TimeUnit.SECONDS.convert(value, timeUnit);
                // 返回转换后的值和单位字符串
                return Tuple.of(result, String.format("%d%s", result, "秒"));
            case "MINUTES":
                // 将分钟转换为目标单位
                result = TimeUnit.MINUTES.convert(value, timeUnit);
                // 返回转换后的值和单位字符串
                return Tuple.of(result, String.format("%d%s", result, "分钟"));
            case "HOURS":
                // 将小时转换为目标单位
                result = TimeUnit.HOURS.convert(value, timeUnit);
                // 返回转换后的值和单位字符串
                return Tuple.of(result, String.format("%d%s", result, "小时"));
            case "DAYS":
                // 将天转换为目标单位
                result = TimeUnit.DAYS.convert(value, timeUnit);
                // 返回转换后的值和单位字符串
                return Tuple.of(result, String.format("%d%s", result, "天"));
            case "MS":
            case "MILLIS":
            case "MILLISECONDS":
                // 对于毫秒，直接返回原始值，因为没有进行转换
            default:
                result = value;
                // 返回原始值和单位字符串
                return Tuple.of(result, String.format("%d%s", result, "毫秒"));
        }
    }


    /**
     * 将给定的时间值转换为毫秒
     * 此方法根据提供的单位，将时间值转换为相应的毫秒数
     * 它支持从秒、分钟、小时、天以及毫秒作为输入单位的转换
     *
     * @param value 待转换的时间值
     * @param unit  时间单位，支持 SECONDS、MINUTES、HOURS、DAYS、MS、MILLIS、MILLISECONDS
     * @return 转换后的毫秒数
     */
    public static Long toMillis(long value, String unit) {
        // 根据提供的单位，将时间值转换为毫秒
        switch (unit.toUpperCase()) {
            case "SECONDS":
                // 如果单位是秒，将秒转换为毫秒
                return TimeUnit.SECONDS.toMillis(value);
            case "MINUTES":
                // 如果单位是分钟，将分钟转换为毫秒
                return TimeUnit.MINUTES.toMillis(value);
            case "HOURS":
                // 如果单位是小时，将小时转换为毫秒
                return TimeUnit.HOURS.toMillis(value);
            case "DAYS":
                // 如果单位是天，将天转换为毫秒
                return TimeUnit.DAYS.toMillis(value);
            case "MS":
            case "MILLIS":
            case "MILLISECONDS":
                // 如果单位已经是毫秒或者未指定单位，则直接返回原始值
            default:
                return value;
        }
    }


    /**
     * 将给定的时间单位字符串转换为对应的时间单位枚举
     * 如果无法识别时间单位，将默认返回毫秒作为时间单位
     *
     * @param unit 时间单位的字符串表示，支持 "SECONDS", "MINUTES", "HOURS", "DAYS", "MS", "MILLIS", "MILLISECONDS"
     * @return 对应的时间单位枚举
     */
    public static TimeUnit getUnit(String unit) {
        // 根据输入的时间单位字符串（不区分大小写）来决定返回的时间单位枚举
        switch (unit.toUpperCase()) {
            case "SECONDS":
                return TimeUnit.SECONDS;
            case "MINUTES":
                return TimeUnit.MINUTES;
            case "HOURS":
                return TimeUnit.HOURS;
            case "DAYS":
                return TimeUnit.DAYS;
            // 以下处理不常用的时间单位字符串，包括拼写缩写和全称
            case "MS":
            case "MILLIS":
            case "MILLISECONDS":
                // 默认情况下返回毫秒，以处理未明确指定或拼写错误的时间单位
            default:
                return TimeUnit.MILLISECONDS;
        }
    }


    /**
     * 获取当前日期所在周的日期范围
     * <p>
     * 该方法用于计算并返回当前日期所在周的起始和结束时间它假设每周从周一到周日为一个周期
     * 如果当前日期为周一，则起始时间为当天的0时0分，结束时间为当天的23时59分
     * 如果当前日期不是周一，则起始时间为当周周一的0时0分，结束时间为当周周日的23时59分
     *
     * @return Tuple2<LocalDateTime, LocalDateTime> 一个元组，包含周起始和结束时间的LocalDateTime对象
     */
    public static Tuple2<LocalDateTime, LocalDateTime> weekRange() {
        return weekRange(LocalDate.now());
    }


    /**
     * 计算给定日期所在周的开始和结束时间
     * 周的开始定义为周一，结束为周日
     * 时间范围从周一的00:00:00.000到周日的23:59:59.999
     *
     * @param date LocalDate类型，表示需要计算周范围的日期
     * @return Tuple2包含两个LocalDateTime对象，分别表示周的开始和结束时间
     */
    public static Tuple2<LocalDateTime, LocalDateTime> weekRange(LocalDate date) {
        // 使用WeekFields设置一周的开始为周一
        TemporalField fieldISO = WeekFields.of(DayOfWeek.MONDAY, 1).dayOfWeek();
        // 将给定日期设置为该周周一的开始时间
        LocalDateTime startTime = date.with(fieldISO, 1).atTime(LocalTime.MIN);
        // 将给定日期设置为该周周日的结束时间
        LocalDateTime endTime = date.with(fieldISO, 7).atTime(LocalTime.MAX);
        // 返回周开始和结束时间的Tuple2对象
        return Tuple.of(startTime, endTime);
    }


    /**
     * 获取当前月份的起始和结束时间
     * 此方法用于快速获取当前月份的时间范围，以LocalDateTime形式表示
     * 主要用于简化获取时间范围的操作，提高代码可读性和可维护性
     *
     * @return Tuple2<LocalDateTime, LocalDateTime> 包含当前月份起始和结束时间的元组
     * 第一个元素是月份的第一天的开始时刻，第二个元素是月份的最后一天的结束时刻
     */
    public static Tuple2<LocalDateTime, LocalDateTime> monthRange() {
        return monthRange(LocalDate.now());
    }


    /**
     * 计算给定日期所在月份的起始和结束时间
     * 此函数的目的是提供一个简便的方法来获取任何给定日期所在月份的第一天和最后一天的完整时间范围
     * 它通过将日期与时间的最小值和最大值结合使用，并应用相应的日期调整器来实现
     *
     * @param date LocalDate对象，表示需要计算月份范围的日期
     * @return Tuple2 包含两个LocalDateTime对象的元组，第一个是月份的第一天的起始时间，第二个是月份的最后一天的结束时间
     */
    public static Tuple2<LocalDateTime, LocalDateTime> monthRange(LocalDate date) {
        // 计算月份的第一天的起始时间
        LocalDateTime startTime = date.atTime(LocalTime.MIN).with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
        // 计算月份的最后一天的结束时间
        LocalDateTime endTime = date.atTime(LocalTime.MAX).with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
        // 返回包含起始和结束时间的元组
        return Tuple.of(startTime, endTime);
    }


    /**
     * 获取当前季度的日期范围
     * <p>
     * 该方法用于确定当前季度的开始和结束时间它默认使用当前日期来计算季度范围
     * 为什么需要这个方法？在进行数据分析或财务报告时，了解当前季度的准确日期范围至关重要
     * 这样可以确保所有相关的数据都被考虑进去，避免报告出现遗漏或错误
     *
     * @return Tuple2<LocalDateTime, LocalDateTime> 一个元组，包含当前季度的开始和结束时间
     * 第一个元素是季度的开始时间，第二个元素是季度的结束时间
     */
    public static Tuple2<LocalDateTime, LocalDateTime> quarterRange() {
        return quarterRange(LocalDate.now());
    }

    /**
     * 获取指定日期所在季度的起始和结束时间范围
     *
     * @param date LocalDate类型的日期，用于计算季度范围
     * @return Tuple2<LocalDateTime, LocalDateTime> 返回一个元组，包含季度的起始和结束时间
     */
    public static Tuple2<LocalDateTime, LocalDateTime> quarterRange(LocalDate date) {
        // 创建QuarterCycle对象，用于处理季度相关计算
        QuarterCycle quarterCycle = new QuarterCycle();
        // 获取指定日期所在的季度
        int quarter = quarterCycle.getQuarter(date);
        // 返回指定年份和季度的时间范围
        return quarterCycle.getQuarterRange(date.getYear(), quarter);
    }

    public static class QuarterCycle implements Serializable {
        /**
         * 定义四个季度枚举值
         */
        public enum QuarterEnum {
            FIRST, SECOND, THIRD, FOURTH
        }

        public static class QuarterOfYearQuery implements TemporalQuery<QuarterEnum> {
            @Override
            public QuarterEnum queryFrom(TemporalAccessor temporal) {
                LocalDate now = LocalDate.from(temporal);
                if (now.isBefore(now.with(Month.APRIL).withDayOfMonth(1))) {
                    return QuarterEnum.FIRST;
                } else if (now.isBefore(now.with(Month.JULY).withDayOfMonth(1))) {
                    return QuarterEnum.SECOND;
                } else if (now.isBefore(now.with(Month.OCTOBER).withDayOfMonth(1))) {
                    return QuarterEnum.THIRD;
                } else {
                    return QuarterEnum.FOURTH;
                }
            }
        }


        /**
         * 根据给定的日期时间获取对应的季度
         * 此方法重载了另一个getQuarter方法，允许直接传入LocalDateTime对象
         * 通过将LocalDateTime转换为LocalDate来确定季度，因为季度的确定不依赖于时间部分
         *
         * @param dateTime 日期时间对象，用于确定季度
         * @return 季度值，返回1到4之间的整数，分别代表一年中的四个季度
         */
        public int getQuarter(LocalDateTime dateTime) {
            // 调用重载方法，使用日期部分来确定季度
            return getQuarter(dateTime.toLocalDate());
        }


        /**
         * 根据给定的日期获取对应的季度
         *
         * @param date LocalDate对象，用于确定日期所在的季度
         * @return 返回一个整数，表示日期所在的季度（1代表第一季度，2代表第二季度，3代表第三季度，4代表第四季度）
         */
        public int getQuarter(LocalDate date) {
            // 创建一个自定义的TemporalQuery对象，用于查询日期所在的季度枚举
            TemporalQuery<QuarterEnum> quarterOfYearQuery = new QuarterOfYearQuery();
            // 使用给定的日期和查询对象来获取季度枚举
            QuarterEnum quarter = date.query(quarterOfYearQuery);
            // 根据季度枚举返回对应的季度编号
            switch (quarter) {
                case FIRST:
                    return 1;
                case SECOND:
                    return 2;
                case THIRD:
                    return 3;
                case FOURTH:
                    return 4;
                default:
                    return 0;
            }
        }


        /**
         * 根据给定的时间和季度获取该季度的起始和结束时间
         * 此方法重载了另一个getQuarterRange方法，专门用于处理LocalDateTime对象
         *
         * @param dateTime 输入的时间，用于确定年份
         * @param quarter  输入的季度，用于与dateTime的年份一起计算季度范围
         * @return 返回一个Tuple2对象，包含指定季度的起始和结束时间
         */
        public Tuple2<LocalDateTime, LocalDateTime> getQuarterRange(LocalDateTime dateTime, int quarter) {
            // 调用重载方法，使用输入时间的年份和季度来获取季度范围
            return getQuarterRange(dateTime.getYear(), quarter);
        }

        /**
         * 获取指定年份和季度的开始和结束时间范围
         * 此方法重载了另一个getQuarterRange方法，允许直接使用LocalDate和季度作为参数
         *
         * @param dateTime 用于确定年份的LocalDate对象，方法仅使用此日期的年份信息
         * @param quarter  指定的季度，必须是1到4之间的整数
         * @return 返回一个Tuple2对象，包含指定季度的开始和结束时间（都是LocalDateTime对象）
         */
        public Tuple2<LocalDateTime, LocalDateTime> getQuarterRange(LocalDate dateTime, int quarter) {
            return getQuarterRange(dateTime.getYear(), quarter);
        }


        /**
         * 获取指定年份和季度的时间范围
         * 该方法根据给定的年份和季度，计算出该季度的开始和结束时间，并以Tuple2对象的形式返回
         *
         * @param year    指定的年份，用于确定季度所属的年份
         * @param quarter 指定的季度，取值范围为1到4，分别代表一年中的四个季度
         * @return 返回一个Tuple2对象，包含指定季度的开始和结束时间
         * @throws RuntimeException 如果季度参数不在1到4的范围内，则抛出运行时异常
         */
        public Tuple2<LocalDateTime, LocalDateTime> getQuarterRange(int year, int quarter) {
            LocalDate startDate, endDate;
            // 根据季度参数确定季度的开始和结束日期
            switch (quarter) {
                case 1:
                    // 01.01-03.31
                    startDate = LocalDate.of(year, 1, 1);
                    endDate = LocalDate.of(year, 3, 31);
                    break;
                case 2:
                    // 04.01-06.30
                    startDate = LocalDate.of(year, 4, 1);
                    endDate = LocalDate.of(year, 6, 30);
                    break;
                case 3:
                    // 07.01-09.30
                    startDate = LocalDate.of(year, 7, 1);
                    endDate = LocalDate.of(year, 9, 30);
                    break;
                case 4:
                    // 10.01-12.31
                    startDate = LocalDate.of(year, 10, 1);
                    endDate = LocalDate.of(year, 12, 31);
                    break;
                default:
                    // 如果季度参数无效，抛出异常
                    throw new RuntimeException("quarter range [1-4]");
            }
            // 返回包含季度开始和结束时间的Tuple2对象
            return Tuple.of(startDate.atTime(LocalTime.MIN), endDate.atTime(LocalTime.MAX));
        }
    }

    /**
     * Created by tao.zeng on 2024/11/7.
     */
    public interface TimestampParser {

        /**
         * Default pattern list.
         *
         * @return the list
         */
        static List<String> defaultPattern() {
            return Lists.newArrayList(
                    "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", // 2020-08-13T08:59:59.904Z
                    "yyyy-MM-dd HH:mm:ss:SSSSSS", // 2020-08-13 08:59:59:904001
                    "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", // 2020-08-13T08:59:59.904+08:00
                    "yyyy-MM-dd HH:mm:ss:SSSSSS", // 2020-08-13 08:59:59:904001
                    "yyyy-MM-dd HH:mm:ss", // 2020-08-13 08:59:59
                    "yyyy-MM-dd HH:mm:ss.SSSZ",
                    "yyyy-MM-dd HH:mm:ss.SSS",
                    "yyyy-MM-dd HH:mm:ss,SSSZ",
                    "yyyy-MM-dd HH:mm:ss,SSS"
            );
        }

        /**
         * Parse local date time.
         *
         * @param pattern the pattern
         * @return the local date time
         */
        LocalDateTime parse(List<DateTimeFormatter> pattern);
    }


    /**
     * Created by tao.zeng on 2024/11/7.
     */
    public static class TimestampParserFactory {

        private final static int UNIX_LENGTH = 10, UNIX_MS_LENGTH = 13;

        /**
         * Make parser timestamp parser.
         *
         * @param value the value
         * @return the timestamp parser
         */
        public static TimestampParser makeParser(String value) {
            if (NumberUtils.isCreatable(value)) {
                long longValue = NumberUtils.createNumber(value).longValue();
                int length = Long.toString(longValue).length();

                if (length == UNIX_LENGTH) {
                    return new UnixEpochParser(longValue);
                } else if (length == UNIX_MS_LENGTH) {
                    return new UnixMillisEpochParser(longValue);
                }
            }
            return new LocalDateTimeParser(value);
        }
    }


    /**
     * Created by tao.zeng on 2024/11/7.
     */
    @Slf4j
    public static class LocalDateTimeParser implements TimestampParser {
        private final String value;

        public LocalDateTimeParser(String value) {
            this.value = value;
        }

        @Override
        public LocalDateTime parse(List<DateTimeFormatter> pattern) {
            DateTimeParseException lastException = null;
            LocalDateTime dateTime = null;
            try {
                dateTime = LocalDateTime.parse(value);
            } catch (DateTimeParseException e) {
                lastException = e;
                for (DateTimeFormatter formatter : pattern) {
                    try {
                        dateTime = LocalDateTime.parse(value, formatter);
                        log.info("match formatter:{}", formatter);
                        break;
                    } catch (DateTimeParseException ex) {
                        ex.addSuppressed(e);
                        lastException = ex;
                    }
                }
            }

            if (Objects.nonNull(dateTime)) return dateTime;

            throw lastException;
        }
    }

    /**
     * Created by tao.zeng on 2024/11/7.
     */
    public static class UnixEpochParser implements TimestampParser {

        private final static long MAX_EPOCH_SECONDS = Integer.MAX_VALUE;

        private final long value;

        public UnixEpochParser(long value) {
            this.value = value;
        }

        @Override
        public LocalDateTime parse(List<DateTimeFormatter> pattern) {
            if (value > MAX_EPOCH_SECONDS) {
                throw new IllegalArgumentException("Cannot parse date for value larger than UNIX epoch maximum seconds");
            }
            Instant instant = DateTimeUtils.ofEpochSecond(value);
            return DateTimeUtils.toJava8(instant);
        }
    }

    /**
     * Created by tao.zeng on 2024/11/7.
     */
    public static class UnixMillisEpochParser implements TimestampParser {

        private final static long MAX_EPOCH_MILLISECONDS = (long) Integer.MAX_VALUE * 1000;

        private final long value;

        public UnixMillisEpochParser(long value) {
            this.value = value;
        }

        @Override
        public LocalDateTime parse(List<DateTimeFormatter> pattern) {
            if (value > MAX_EPOCH_MILLISECONDS) {
                throw new IllegalArgumentException("Cannot parse date for value larger than UNIX epoch maximum seconds");
            }
            Instant instant = DateTimeUtils.ofEpochMilli(value);
            return DateTimeUtils.toJava8(instant);
        }
    }
}
